Skip to content

fix(go-extractor): attach methods on generic receivers and handle grouped type declarations#445

Open
tirth8205 wants to merge 1 commit into
Egonex-AI:mainfrom
tirth8205:fix/go-extractor-generics-grouped-types
Open

fix(go-extractor): attach methods on generic receivers and handle grouped type declarations#445
tirth8205 wants to merge 1 commit into
Egonex-AI:mainfrom
tirth8205:fix/go-extractor-generics-grouped-types

Conversation

@tirth8205

Copy link
Copy Markdown
Contributor

Problem

Two distinct gaps in the Go extractor caused real-world Go constructs to be silently dropped from the structural graph:

  1. Methods on generic receivers were never attached to their struct. extractReceiverType only handled a bare type_identifier receiver or a pointer_type directly wrapping a type_identifier. For a generic type the receiver is a generic_type node — func (s Stack[T]) ... parses as parameter_declaration -> generic_type -> {type_identifier 'Stack', type_arguments} and func (s *Stack[T]) ... as pointer_type -> generic_type -> type_identifier. Neither matched, so the function returned undefined, nothing was recorded in methodsByReceiver, and the struct's methods array stayed empty. A Stack[T] struct with Push/Len methods produced classes[0].methods === [].

  2. Grouped type declarations dropped every type after the first. extractTypeDeclaration used findChild(node, "type_spec"), which returns only the first type_spec. Go allows grouping types under one type ( ... ) block, which parses as one type_declaration with several type_spec children. So type ( Foo struct{...}; Bar struct{...} ) produced classes: ['Foo'] and exports: ['Foo'], with Bar missing entirely.

Fix

  • extractReceiverType: iteratively unwrap pointer_type and generic_type layers (descending generic_type via its type field) before grabbing the base type_identifier. Non-generic receivers are unaffected.
  • extractTypeDeclaration: iterate over all type_spec children via findChildren (already imported), and pass each typeSpec as the decl node so line ranges are per-type rather than the whole group.

Both changes are minimal and localized to go-extractor.ts.

Testing

Added two tests to go-extractor.test.ts:

  • attaches methods on generic receivers to their struct — a Stack[T] struct with *Stack[T] and Stack[T] receivers; expects classes[0].methods to contain Push and Len.
  • handles grouped type declarations — a type ( Foo struct{...}; Bar struct{...} ) block; expects classes to be ['Foo','Bar'] and exports to contain both.

Both tests fail before the fix (methods is []; only Foo is captured) and pass after. The full core Vitest suite is green (694 tests), tsc --noEmit exits 0, and ESLint passes on both changed files.

🤖 Generated with Claude Code

…uped type declarations

extractReceiverType only unwrapped a bare type_identifier or a
pointer_type directly wrapping one. Generic receivers like
`(s *Stack[T])` or `(s Stack[T])` are parsed as generic_type nodes
(optionally pointer-wrapped), so the base type name was never found
and the method was never attached to its struct (methods stayed []).
The fix iteratively unwraps pointer_type and generic_type layers
before grabbing the base type_identifier.

extractTypeDeclaration used findChild(node, "type_spec"), which only
returns the first type_spec. Grouped declarations
(`type ( Foo struct{...}; Bar struct{...} )`) contain multiple
type_spec children, so every type after the first was silently
dropped from classes and exports. The fix iterates over all type_spec
children via findChildren and passes each typeSpec as the decl node
for accurate per-type line ranges.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

@thejesh23 thejesh23 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

1. Grouped type aliases / non-struct-non-interface specs are silently dropped.
extractTypeDeclaration now iterates all type_spec children but still only branches on struct_type / interface_type. A type ( Foo struct{...}; MyID = string; Count int ) block records Foo and drops MyID and Count without any signal — the same silent-drop pattern the PR is fixing for grouped structs. Worth either handling type aliases/named primitives or at least a comment noting the gap.

2. Test coverage for grouped declarations is shallow.
Only an all-struct group is exercised. A grouped block mixing struct and interface (e.g. type ( Foo struct{...}; Bar interface{...} )), or two grouped interfaces, would catch regressions in the interface branch of the new loop — currently nothing pins extractInterface(typeSpec, ...) against the per-spec node change.

3. Multi-type-parameter and constrained generic receivers are untested.
The new test covers Stack[T] only. func (s *Box[K, V]) ... and func (s Result[T comparable]) ... are common Go 1.18+ patterns; both should fall out of the same generic_type -> type field traversal, but without a test there's nothing guarding it. (Same class of issue you'll hit in #435 for Dart generic type parameters on methods — worth keeping the receiver-unwrap pattern consistent across extractors.)

Nit: extractTypeDeclaration now passes typeSpec (not the outer type_declaration) as declNode, so for non-grouped type Foo struct{...} the recorded lineRange[0] is the name line rather than the type keyword line. Existing tests happen to pass because type and the name are on the same line, but the semantics shifted silently.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants